package com.njbw.design_pattern.struct.proxy.dynamicProxy.jdk;

import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;

/**
 *
 * @author 黄侯淳
 * @since JDK 1.8
 */
@Slf4j
public class CacheInvocationHandler implements InvocationHandler {

    // 可以自行改造成为具有 LRU 淘汰功能的缓存
    private final HashMap<String, String> cache = new LinkedHashMap<>(256);

    // 被代理的实例，需要持有它来调用它的方法
    private final DataQuery dataQuery;

    public CacheInvocationHandler() {
        this.dataQuery = new DatabaseDataQuery();
    }

    public CacheInvocationHandler(DataQuery dataQuery) {
        this.dataQuery = dataQuery;
    }

    /**
     * Processes a method invocation on a proxy instance and returns
     * the result.  This method will be invoked on an invocation handler
     * when a method is invoked on a proxy instance that it is
     * associated with.
     *
     * @param proxy  the proxy instance that the method was invoked on
     * @param method the {@code Method} instance corresponding to
     *               the interface method invoked on the proxy instance.  The declaring
     *               class of the {@code Method} object will be the interface that
     *               the method was declared in, which may be a superinterface of the
     *               proxy interface that the proxy class inherits the method through.
     * @param args   an array of objects containing the values of the
     *               arguments passed in the method invocation on the proxy instance,
     *               or {@code null} if interface method takes no arguments.
     *               Arguments of primitive types are wrapped in instances of the
     *               appropriate primitive wrapper class, such as
     *               {@code java.lang.Integer} or {@code java.lang.Boolean}.
     * @return the value to return from the method invocation on the
     * proxy instance.  If the declared return type of the interface
     * method is a primitive type, then the value returned by
     * this method must be an instance of the corresponding primitive
     * wrapper class; otherwise, it must be a type assignable to the
     * declared return type.  If the value returned by this method is
     * {@code null} and the interface method's return type is
     * primitive, then a {@code NullPointerException} will be
     * thrown by the method invocation on the proxy instance.  If the
     * value returned by this method is otherwise not compatible with
     * the interface method's declared return type as described above,
     * a {@code ClassCastException} will be thrown by the method
     * invocation on the proxy instance.
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log.info("method: {}, args: {}", method.getName(), Arrays.toString(args));
        // 1.判断是哪一个方法，只对 query 方法进行缓存
        String result;
        if ("query".equals(method.getName())) {
            // 2.先从缓存中查询，命中直接返回
            result = cache.get(args[0].toString());
            if (null != result) {
                log.info("缓存命中，直接返回缓存结果...");
                return result;
            }

            // 3.缓存未命中，查询数据库（需要被代理实例）
            result = (String) method.invoke(dataQuery, args);

            // 4.如果查询到了，需要进行缓存
            if (null != result) {
                cache.put(args[0].toString(), result);
            }
            return result;
        }

        // 当其它的方法被调用，不希望被干预/增强，直接调用被代理实例的原生方法
        return method.invoke(dataQuery, args);
    }
}
